﻿using System;
using Dewey.Client;

namespace Dewey.Objects
{
    /// <summary>
    ///  The base class of all entity classes that serve as data model interacting with UI with 
    ///  business logic and assocations built in not suitable to work directly with database
    /// </summary>
    public abstract class DeweyObject
    {
        #region Fields

        /// <summary>
        ///  The backing field of Session
        /// </summary>
        private ISession _session;

        #endregion

        #region Constructors

        /// <summary>
        ///  Default parameterless constructor
        /// </summary>
        /// <remarks>
        ///  Id is always set explicitly through the property
        ///  This constructor can be called by the persister and the state is fine to be left as Synced
        ///  If it's called by user code then either the session (normally that's the case) or the user code
        ///  should make sure the id is generated properly and the state is udpated as appropriate
        /// </remarks>
        protected DeweyObject()
        {
            DeweyState = ObjectStates.Synced;
        }

        #endregion

        #region Properties

        /// <summary>
        ///  ID unique within the Dewey Id type
        /// </summary>
        public virtual long Id { get; set; }

        /// <summary>
        ///  The state of the object
        /// </summary>
        public ObjectStates DeweyState { get; set; }

        /// <summary>
        ///  Number of references held by the tracking system
        /// </summary>
        public int HardReferenceCount { get; private set; }

        /// <summary>
        ///  The reference to the corresponding POCO in case it's needed
        /// </summary>
        /// <remarks>
        ///  NOTE during the life cycle of a DeweyObject the POCO is not necessarily always there
        ///  in fact, most of the time it's not, and theoretically it exists only within the transaction scopes
        ///  so the user can't assume the reference is always available referencing the POCO the dewey object represents
        ///  and currently the ONLY use is as a temporary link to the poco that's persisting a newly created 
        ///  object with zero ID to be generated by the DB!
        /// </remarks>
        public BasePoco Poco { get; protected set; }

        /// <summary>
        ///  The session the object is in
        /// </summary>
        public virtual ISession Session 
        {
            get
            {
                return _session;
            }
            set
            {
                if (_session != value)
                {
                    if (_session != null && DeweyState != ObjectStates.Synced)
                    {
                        // changing session rarely happens though
                        _session.CancelObjectUpdate(this);
                    }
                    _session = value;
                    UpdateState();
                }
            }
        }

        #endregion

        #region Methods

        /// <summary>
        ///  This method is called first object iteration for loading data for populating non-relational fields for this entity
        /// </summary>
        /// <param name="poco">The poco to retrieve data from</param>
        public virtual void CopyFromPocoFields(BasePoco poco)
        {
            Id = poco.Id;
        }

        /// <summary>
        ///  This method is called second object iteration for loading data for building up associations for this entity
        /// </summary>
        /// <param name="poco">The poco to retrieve data from if any</param>
        /// <param name="context">
        ///  The context that provides object id lookup from the entity graph perspective 
        ///  and other association related information
        /// </param>
        public virtual void CopyFromPocoRelations(BasePoco poco, ICopyFromPocoContext context)
        {
        }

        /// <summary>
        ///  Called to copy data from the entity to the poco
        /// </summary>
        /// <param name="poco">The poco to copy to</param>
        /// <param name="context">The context that provides object id lookup from the poco/db perspective</param>
        public virtual void CopyToPoco(BasePoco poco, ICopyToPocoContext context)
        {
            poco.Id = Id;
        }

        /// <summary>
        ///  The corresponding poco type
        /// </summary>
        /// <returns>The poco type</returns>
        public abstract Type GetPocoType();

        /// <summary>
        ///  The class that is either this class or its base class of which all derived classes the same Id generator
        ///  and therefore have unique Ids generation enforced
        /// </summary>
        /// <returns>The type of that class</returns>
        public abstract Type GetDeweyIdType();

        /// <summary>
        ///  Instantiate a poco (using its default parameterless constructor)
        /// </summary>
        /// <returns>A poco</returns>
        public virtual BasePoco InstantiatePoco()
        {
            Poco = (BasePoco)Activator.CreateInstance(GetPocoType());
            return Poco;
        }

        /// <summary>
        ///  Requests the object to be saved/added
        /// </summary>
        public virtual void Save()
        {
            DeweyState = ObjectStates.Dirty;
            UpdateState();
        }

        /// <summary>
        ///  Requests the object to be deleted
        /// </summary>
        public virtual void Delete()
        {
            DeweyState = ObjectStates.Deleted;
            UpdateState();
        }

        /// <summary>
        ///  Cancels the previous add/save/delete request and therefore the state becomes synced again
        /// </summary>
        public void CancelUpdate()
        {
            DeweyState = ObjectStates.Synced;
            UpdateState();
        }

        /// <summary>
        ///  Action according to the state 
        /// </summary>
        private void UpdateState()
        {
            if (Session != null)
            {
                switch (DeweyState)
                {
                    case ObjectStates.Dirty:
                        Session.AddDirtyObject(this);
                        break;
                    case ObjectStates.Deleted:
                        Session.RemoveObject(this);
                        break;
                    case ObjectStates.Synced:
                        Session.CancelObjectUpdate(this);
                        break;
                }
            }
        }

        /// <summary>
        ///  Increase the hard reference count
        /// </summary>
        public void AddHardReference()
        {
            HardReferenceCount++;
        }

        /// <summary>
        ///  Decrease the hard reference count
        /// </summary>
        public void ReleaseHardReference()
        {
            if (HardReferenceCount > 0)
            {
                HardReferenceCount--;
            }
        }

        #endregion
    }
}
